Skip to content

feat: add grok agent (official xAI Grok CLI) - #404

Merged
lroolle merged 2 commits into
mainfrom
feat/grok-agent-403
Jul 10, 2026
Merged

feat: add grok agent (official xAI Grok CLI)#404
lroolle merged 2 commits into
mainfrom
feat/grok-agent-403

Conversation

@lroolle

@lroolle lroolle commented Jul 9, 2026

Copy link
Copy Markdown
Member

Adds grok as the fourth first-party agent: xAI's official Grok CLI (x.ai/cli, npm @xai-official/grok, closed source). Not the community superagent-ai/grok-cli (grok-dev) — different product, same bin name; that one stays out.

Close #403

What

  • agents/grok.shoauth default (mounts ~/.grok; first login inside a container via grok login --device-auth), api-key alt (XAI_API_KEY from console.x.ai). Appends --always-approve (grok's bypassPermissions): container is the sandbox, same as claude/codex/gemini.
  • deva.sh.grok canonical entry, autolink ~/.grok into the config root, XAI_API_KEY env filtering per auth mode, default-auth maps.
  • api-key mode mounts no ~/.grok (per Add grok agent: integrate official xAI Grok CLI (@xai-official/grok) as fourth first-party agent #403 spec; confirmed by Codex review). grok credential priority is model.api_key > model.env_key > session token > XAI_API_KEY, so anything a mounted config carries could outrank — and bill instead of — the exported key. The blank auth.json overlay stays as defense for explicit -v mounts.
  • Binary shadowing, both directions. Inbound: grok's npm bin is a node trampoline that resolves ~/.grok/bin/grok FIRST, and postinstall parks the ~125MB real binary there — inside the very dir we bind-mount for auth. pin_grok_platform_binary() moves the binary to ~/.local/bin, repoints the npm bin, removes ~/.grok/bin. Outbound (found in review, reproduced against real 0.2.93): a mounted config.toml without postinstall's [cli] installer = "npm" marker flips grok's updater to installer=internal, and grok update re-creates ~/.grok/bin + ~/.grok/downloads on the host mount — a Linux binary that breaks a macOS host CLI. Fix: append_grok_update_guard overlays both dirs with container-local tmpfs whenever a host dir is mounted at /home/deva/.grok; updater writes die with the container.
  • docker-entrypoint.sh — grok version header, verbose diagnostics, binary presence check.
  • GROK_CLI_VERSION=0.2.93 pinned through versions.env, Makefile, Dockerfile(+rust), version-pins/upgrade/report/resolve scripts, ci/nightly/release workflows, install.sh agent file list. make versions-up and nightly resolve pick up upstream releases via the grok-cli registry entry.

Tests

  • tests/test_release_utils.sh: registry now 8 tools (58/58 pass)
  • tests/version-upgrade.sh: grok mock + build-arg assertions for main and rust builds
  • scripts/test-install-agent-tooling.sh: asserts binary moved to .local/bin, npm bin repointed, ~/.grok/bin gone
  • scripts/test-mount-shape.sh: grok in hybrid/default mount loops; asserts tmpfs update guard present with a mounted .grok and absent (plus no .grok mount at all) in api-key mode
  • scripts/test-version-targets.sh: GROK_CLI_VERSION override/absence assertions
  • CI smoke runs deva.sh grok -Q -- --version alongside the other agents
  • Local: shellcheck (severity=error) clean; real install_npm_agent_tooling run against npmjs on linux/arm64 installs and verifies all four CLIs; updater write-back reproduced and the tmpfs guard verified live against the real docker daemon (mount stack + host dir clean after in-container writes)

Notes

  • The proxy-aware build/version-resolution work that briefly rode on this branch (b6360a6) is split out to its own issue/PR — it broke Basic Tests (stale fixtures) and has independent gaps (host-gateway mapping, credential logging) that deserve their own review.
  • Pre-existing gaps, untouched: scripts/version-upgrade.sh _manifest_pairs never listed Gemini; entrypoint verbose diagnostics also predate gemini (its fallback reports codex). Both worth a small follow-up.
  • In-container grok update downloads into container-local tmpfs and cannot touch the host install; the image pin (GROK_CLI_VERSION) is the only version that matters.

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings July 9, 2026 11:25
@claude-yolo

claude-yolo Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Claude finished @lroolle's task in 1s —— View job


I'll analyze this and get back to you.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Grok as a first-party agent in deva by integrating xAI’s official (closed-source) Grok CLI (@xai-official/grok) with pinned versioning, auth-mode wiring, and a safeguard against ~/.grok/bin host shadowing the in-image binary.

Changes:

  • Introduces a new grok agent module with oauth (default) and api-key (XAI_API_KEY) auth modes and --always-approve.
  • Pins GROK_CLI_VERSION and wires it through build args/labels, version tooling, and CI/nightly/release workflows.
  • Updates install tooling to “pin” the Grok platform binary into ~/.local/bin and remove ~/.grok/bin to avoid host-mounted binary shadowing.

Reviewed changes

Copilot reviewed 29 out of 29 changed files in this pull request and generated no comments.

Show a summary per file
File Description
versions.env Adds GROK_CLI_VERSION pin.
tests/version-upgrade.sh Extends version-upgrade test mocks/assertions to include Grok.
tests/test_release_utils.sh Updates tool registry expectations to include grok-cli.
scripts/version-upgrade.sh Adds Grok to version resolution, manifest output, and docker build args.
scripts/version-pins.sh Adds GROK_CLI_VERSION to the pin variable list.
scripts/update-version-pins.sh Fetches/refreshes GROK_CLI_VERSION from npm.
scripts/test-version-targets.sh Extends version-target tests to cover GROK_CLI_VERSION.
scripts/test-install-agent-tooling.sh Adds Grok stubs and assertions for the pin_grok_platform_binary behavior.
scripts/resolve-tool-versions.sh Resolves grok_cli_version via registry tooling.
scripts/release-utils.sh Adds grok-cli to the tool registry and display names.
scripts/install-agent-tooling.sh Installs @xai-official/grok and pins the platform binary to prevent host shadowing.
README.md Documents Grok as a supported agent and adds invocation examples.
Makefile Adds GROK_CLI_VERSION build arg plumbing and help text.
install.sh Includes agents/grok.sh in installed agent files.
docs/quick-start.md Adds Grok to the installed files list and shows API-key usage example.
docs/how-it-works.md Documents .grok canonical entry and config-home layouts.
docs/authentication.md Documents Grok auth modes and the .grok/bin binary-shadowing mitigation.
docs/advanced-usage.md Updates deva-root layout examples to include grok/.
Dockerfile.rust Adds GROK_CLI_VERSION ARG and image label.
Dockerfile Adds GROK_CLI_VERSION ARG and image label for main image.
deva.sh Adds .grok canonical wiring, autolink, env filtering, and auth overlay target for Grok.
DEV-LOGS.md Records a dev log entry describing the Grok integration and rationale.
CHANGELOG.md Adds an Unreleased entry for the new grok agent feature.
agents/shared_auth.sh Enables Grok auth parsing and default auth method mapping.
agents/grok.sh New agent definition with oauth/api-key auth wiring and --always-approve.
.github/workflows/release.yml Wires Grok version output and build args into release workflow.
.github/workflows/nightly-images.yml Wires Grok version resolution and build args into nightly workflow.
.github/workflows/ci.yml Adds deva.sh grok -Q -- --version to CI smoke checks and passes Grok build arg.
.deva.example Updates hybrid config-home documentation to include Grok.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 59419988de

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread deva.sh
grok)
# grok prefers the auth.json session token over XAI_API_KEY, so the
# blank overlay is what makes api-key auth win over a mounted ~/.grok.
printf '%s' "/home/deva/.grok/auth.json"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Prevent Grok config keys from overriding api-key auth

In api-key mode with an existing ~/.grok/config.toml that sets a per-model api_key or env_key, this overlay only hides the session file while the rest of the mounted .grok directory remains visible, so Grok can still ignore the XAI_API_KEY passed by deva. xAI's auth docs list credential priority as model.api_key > model.env_key > active session token > XAI_API_KEY, so users trying to switch billing/projects with --auth-with api-key can silently run against the wrong key; also mask/override config credentials or avoid mounting .grok for this mode.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 7ad054b, and you were right — masking auth.json alone was insufficient. api-key mode now mounts no ~/.grok at all (matching #403's original spec: "pass XAI_API_KEY, no auth-dir mount"). Precedence confirmed against docs.x.ai: model.api_key > model.env_key > session token > XAI_API_KEY. The blank auth.json overlay stays as defense for explicit -v mounts. test-mount-shape.sh now asserts zero .grok mounts in api-key mode.

Copilot AI review requested due to automatic review settings July 10, 2026 01:51
@claude-yolo

claude-yolo Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Claude finished @lroolle's task in 1s —— View job


I'll analyze this and get back to you.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 29 out of 30 changed files in this pull request and generated 7 comments.

Comment on lines 21 to 25
fetch_npm_version() {
npm view "$1" version 2>/dev/null || true
curl -fsSL --max-time 10 \
"https://registry.npmjs.org/-/package/$1/dist-tags" 2>/dev/null | \
sed -n 's/.*"latest":"\([^"]*\)".*/\1/p'
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested live: the unencoded scoped path returns HTTP 200 with identical output to the %2f form (curl https://registry.npmjs.org/-/package/@xai-official/grok/dist-tags), so the 404 claim doesn't hold. The set -e claim doesn't apply here either: the substitution is passed as an argument to refresh_pin, so a curl failure can't abort the script and the old pin is kept. Moot for this PR regardless — the curl-based fetch was part of the proxy commit, now split out to #407.

Comment thread scripts/release-utils.sh Outdated
Comment on lines +178 to +182
_npm_registry_latest() {
curl -fsSL --max-time 10 \
"https://registry.npmjs.org/-/package/$1/dist-tags" 2>/dev/null | \
sed -n 's/.*"latest":"\([^"]*\)".*/\1/p'
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Half right. Encoding claim is false (tested live, both forms return 200). The failure-handling concern is real though: fetched=$(fetch_latest_version ...) is a plain assignment under set -euo pipefail, so a transient curl failure would abort the report and dead-code the warn-and-fallback branch below it. The curl change moved to #407 and will re-land with || echo "" plus an outage fixture.

Comment thread scripts/install-agent-tooling.sh Outdated
Comment on lines +121 to +132
log_proxy_config() {
local has_proxy=0
for var in HTTP_PROXY HTTPS_PROXY http_proxy https_proxy NO_PROXY no_proxy; do
if [ -n "${!var:-}" ]; then
log " $var=${!var}"
has_proxy=1
fi
done
if [ "$has_proxy" -eq 0 ]; then
log " (no proxy configured)"
fi
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid — and broader than flagged: version-upgrade.sh echoes the generated proxy args and make echoes recipes, same exposure. The proxy work is split out to #407; redaction of proxy userinfo across all three sites is in its test plan.

Comment thread Makefile Outdated
$(if $(NO_PROXY),--build-arg NO_PROXY=$(NO_PROXY)) \
$(if $(no_proxy),--build-arg no_proxy=$(no_proxy))

DOCKER_BUILD_FLAGS = $(PROXY_BUILD_ARGS) $(DOCKER_BUILD_EXTRA_ARGS)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid — one root cause across these four comments: the rewrite needs --add-host host.docker.internal:host-gateway on the docker build side for native Linux Engine (Desktop/OrbStack resolve it implicitly). Proxy work split out to #407, which fixes this once in the shared flags, plus the userinfo-blind rewrite (://user:pass@127.0.0.1 is not matched by the current sed).

Comment on lines 224 to 226
docker build -f "$DOCKERFILE" \
${PROXY_ARGS[@]+"${PROXY_ARGS[@]}"} \
--target agent-base \

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid — one root cause across these four comments: the rewrite needs --add-host host.docker.internal:host-gateway on the docker build side for native Linux Engine (Desktop/OrbStack resolve it implicitly). Proxy work split out to #407, which fixes this once in the shared flags, plus the userinfo-blind rewrite (://user:pass@127.0.0.1 is not matched by the current sed).

Comment on lines 238 to 240
docker build -f "$DOCKERFILE" \
${PROXY_ARGS[@]+"${PROXY_ARGS[@]}"} \
--build-arg NODE_MAJOR="$NODE_MAJOR" \

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid — one root cause across these four comments: the rewrite needs --add-host host.docker.internal:host-gateway on the docker build side for native Linux Engine (Desktop/OrbStack resolve it implicitly). Proxy work split out to #407, which fixes this once in the shared flags, plus the userinfo-blind rewrite (://user:pass@127.0.0.1 is not matched by the current sed).

Comment on lines 257 to 259
docker build -f "$RUST_DOCKERFILE" \
${PROXY_ARGS[@]+"${PROXY_ARGS[@]}"} \
--build-arg BASE_IMAGE="$CORE_IMAGE" \

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid — one root cause across these four comments: the rewrite needs --add-host host.docker.internal:host-gateway on the docker build side for native Linux Engine (Desktop/OrbStack resolve it implicitly). Proxy work split out to #407, which fixes this once in the shared flags, plus the userinfo-blind rewrite (://user:pass@127.0.0.1 is not matched by the current sed).

lroolle and others added 2 commits July 9, 2026 19:59
- agents/grok.sh: oauth default (mount ~/.grok; in-container login via
  grok login --device-auth) and api-key (XAI_API_KEY); appends
  --always-approve since the container is the sandbox
- deva.sh: .grok canonical entry, autolink, XAI_API_KEY env filtering,
  blank auth.json overlay for api-key mode (grok prefers the session
  token over the env key; the overlay makes the key actually win)
- install tooling: grok's npm bin is a trampoline that resolves
  ~/.grok/bin/grok first, and postinstall parks the ~125MB real binary
  there (self-update dir); move it to ~/.local/bin and drop ~/.grok/bin
  so a host-mounted ~/.grok (possibly a macOS binary) can never shadow
  the image CLI
- pin GROK_CLI_VERSION=0.2.93 through versions.env, Makefile,
  Dockerfile(+rust), version scripts, ci/nightly/release workflows,
  install.sh agent list
- tests: registry, version-upgrade, install-tooling, version-targets
- docs: README, authentication, quick-start, how-it-works, changelog

Close #403

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review hardening for #404 (thanks Codex + two deep review passes):

- tmpfs over ~/.grok/bin and ~/.grok/downloads whenever a host dir is
  mounted at /home/deva/.grok. Reproduced against real 0.2.93: a mounted
  config.toml without postinstall's `[cli] installer = "npm"` marker
  flips the updater to installer=internal, and `grok update` re-creates
  both dirs ON THE HOST MOUNT — a Linux binary that shadows a macOS
  host CLI via the host npm trampoline. Updater writes now die with
  the container; verified live (mount stack + host-side clean).
- api-key mode mounts no ~/.grok, per the #403 spec. grok credential
  priority is model.api_key > model.env_key > session token >
  XAI_API_KEY, so anything a mounted config carries can outrank — and
  bill instead of — the exported key. Blank auth.json overlay kept as
  defense for explicit -v mounts.
- docker-entrypoint.sh: grok version header, verbose diagnostics, and
  binary presence check (#403 scope, was missing).
- test-mount-shape.sh: grok in hybrid/default loops; new assertions for
  the update guard and api-key no-mount.
- docs/authentication.md, CHANGELOG, DEV-LOGS: correct the false
  "grok update is a no-op" claim; document the real behavior.

Refs #403

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 10, 2026 03:13
@lroolle
lroolle force-pushed the feat/grok-agent-403 branch from b6360a6 to 7ad054b Compare July 10, 2026 03:13
@claude-yolo

claude-yolo Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Claude finished @lroolle's task in 1s —— View job


I'll analyze this and get back to you.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 31 out of 31 changed files in this pull request and generated 1 comment.

Comment thread DEV-LOGS.md
- deva.sh append_grok_update_guard: the reverse hole, found in review and reproduced against the real 0.2.93 — a mounted config.toml without the postinstall's `[cli] installer = "npm"` marker flips the updater to installer=internal, and `grok update` re-creates ~/.grok/bin + ~/.grok/downloads ON THE HOST MOUNT (Linux binary shadows a macOS host CLI via the host trampoline). Fix: tmpfs over both dirs whenever a host dir lands at /home/deva/.grok; updater writes die with the container
- docker-entrypoint.sh: grok version header, verbose diagnostics, ensure_agent_binaries
- GROK_CLI_VERSION pin wired through versions.env, Makefile, Dockerfile(+rust), version-pins/upgrade/report/resolve scripts, ci/nightly/release workflows; tests extended (release-utils registry, version-upgrade mock, install-tooling, version-targets, mount-shape grok + update-guard + api-key no-mount assertions)
- Result: deva.sh grok works with both auth modes (dry-run + mount-shape verified); grok pinned at 0.2.93; make versions-up picks up new upstream releases; host ~/.grok cannot be poisoned by in-container updates (tmpfs guard verified live: uid/gid/mode/size + noexec)
@lroolle
lroolle merged commit 7a8f094 into main Jul 10, 2026
22 checks passed
@lroolle
lroolle deleted the feat/grok-agent-403 branch July 10, 2026 03:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add grok agent: integrate official xAI Grok CLI (@xai-official/grok) as fourth first-party agent

2 participants